Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updates on diagnostics (log + new diagnostics) #1085

Merged
merged 7 commits into from
Dec 24, 2020

Conversation

mockersf
Copy link
Member

@mockersf mockersf commented Dec 18, 2020

  • move diagnostics to log now that we have a log framework setup
  • add entity count diagnostic
  • add asset count diagnostic

I found those two diagnostics useful while debugging game that slowed down over time, to track lost entities or extra assets (less true with new auto cleanup assets)

@Moxinilian Moxinilian added A-Core Common functionality for all bevy apps C-Enhancement A new feature labels Dec 18, 2020
/// State used by the [PrintDiagnosticsPlugin]
pub struct PrintDiagnosticsState {
/// State used by the [LogDiagnosticsPlugin]
pub struct LogDiagnosticsState {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pub but is not exported from the crate

Maybe use pub(crate)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be pub because it's in a public interface:

crate-visible type `LogDiagnosticsState` in public interface
can't leak crate-visible type

It's public, just not user addressable...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we stop exporting the systems, then that error goes away. I'm not sure why we would be exporting the systems anyway?

It was created as public in 704a742, which was in May. I suspect it's just an oversight.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pubs are removed

if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) {
Self::print_diagnostic(diagnostic);
Self::log_diagnostic(diagnostic);
}
} else {
for diagnostic in diagnostics.iter() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these be sorted so that the diagnostics are in a consistent order?

It's not helpful every time you restart the app getting a different order.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently you can have them ordered if you use a filter, as the filter is stored in a Vec and then iterated over.

Without filter, the diagnostics are stored in a HashMap and that doesn't keep order... We could switch to a BTreeMap, or add a Vec to keep order of insertion and use it to iterate... What do you think?

Copy link
Member

@DJMcNab DJMcNab Dec 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So BTreeMap would probably be a good start, although a Vec sorted by name or by insertion order could both also be a good choice.

Basically, anything deterministic would be better than the current situation.

Edit: Given that there are generally only a handful of diagnostic types, it might make sense to just use a Vec to store them anyway and binary search to get a specific diagnostic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a BTreeMap. After looking a little into a Vec solution, I didn't like it as it would have mean every diagnostic recording a value would have to go through the Vec...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O(logn) is better than O(n), but for a large number of diagnostics idk if its a good idea to replace a hash map. While we currently only have a small number, I don't want to assume that will always be the case. Maybe LogDiagnosticsPlugin could store its own order? Sorting is a behavior inherent to the LogDiagnosticsPlugin so that makes sense to me.

The issue is keeping the order synced up / accounting for new diagnostics. Lol we could have a Diagnostic event. Ex: DiagnosticEvent::Added(DiagnosticId), DiagnosticEvent::Removed(DiagnosticId), etc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into adding an event, seems not easy as adding a diagnostics can happen at app build time by mutating a resource, so the new diagnostic plugin would be responsible from calling the event (I think)
Keeping the order in LogDiagnosticsPlugin without events seemed not pretty
I added a BTreeSet of the DiagnosticId to Diagnostics that is updated when adding a diagnostics, and that is used for iterating orderedly

@DJMcNab
Copy link
Member

DJMcNab commented Dec 22, 2020

I have confirmed that the change does make the order consistent, which is definitely an improvement. I'm less sure about the default output format, just because there's an awful lot of noise.

@mockersf
Copy link
Member Author

output of example log_diagnostics:

Dec 22 14:34:03.374  INFO bevy_diagnostic::log_diagnostics_plugin: frame_count                                                      : 58.000000   (avg 58.000000)
Dec 22 14:34:03.374  INFO bevy_diagnostic::log_diagnostics_plugin: frame_time                                                       : 0.015162    (avg 0.016622)
Dec 22 14:34:03.375  INFO bevy_diagnostic::log_diagnostics_plugin: fps                                                              : 60.642875   (avg 60.021922)

before, it was:

Diagnostics:
---------------------------------------------------------------------------------------------
frame_time                                                       : 0.017387    (avg 0.016682)

fps                                                              : 60.059073   (avg 59.994266)

frame_count                                                      : 58.000000   (avg 58.000000)

Copy link
Member

@cart cart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good calls all around!


pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, query: Query<Entity>) {
let mut count = 0.;
for _entity in &mut query.iter() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could replace this with let count = query.iter().count(). Thats simpler, but also if/when we re-implement ExactSizeIterator for query it could make this O(1)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or alternatively we could make it O(1) now by adding a world.entity_count() function and making this system thread-local.

Your call!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried adding it to World, in my test with a system adding and removing entities every frame, the two counts are the same

if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) {
Self::print_diagnostic(diagnostic);
Self::log_diagnostic(diagnostic);
}
} else {
for diagnostic in diagnostics.iter() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O(logn) is better than O(n), but for a large number of diagnostics idk if its a good idea to replace a hash map. While we currently only have a small number, I don't want to assume that will always be the case. Maybe LogDiagnosticsPlugin could store its own order? Sorting is a behavior inherent to the LogDiagnosticsPlugin so that makes sense to me.

The issue is keeping the order synced up / accounting for new diagnostics. Lol we could have a Diagnostic event. Ex: DiagnosticEvent::Added(DiagnosticId), DiagnosticEvent::Removed(DiagnosticId), etc

@cart
Copy link
Member

cart commented Dec 24, 2020

I do think we might want to try cutting down on the line-noise of the logs a bit (maybe by printing the whole table as a single log).

I also think we could probably maintain an up-to-date "entity count" number within Entities::flush to avoid the need to do atomic ops every time entity_count is needed (I had it in my head that this number already exists).

But for now this all looks good.

@cart cart merged commit b28365f into bevyengine:master Dec 24, 2020
@mockersf mockersf deleted the print-to-log branch April 27, 2021 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Core Common functionality for all bevy apps C-Enhancement A new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants